//################################################################################
// MIT License
// Copyright (c) 2024 ZhangYihua
//
// Change Logs:
// Date           Author       Notes
// 2020-02-06     ZhangYihua   first version
//
// Description  : 
//################################################################################

module prbs_chk #(
parameter           PRBS_N                  = 23,
parameter           IN_DW                   = 8,
parameter           SHIFT_N                 = IN_DW,  // must SHIFT_N<=IN_DW
parameter           PRBS_INI                = 1'b0,   // 1'b0: all zeros is a normal state and all ones  is a dead state for prbs_dat; 
                                                      // 1'b1: all ones  is a normal state and all zeros is a dead state for prbs_dat; 
parameter           MSB_FIRST               = 1'b1,
parameter           SYNC_NUM                = 1 + ((PRBS_N+8)/IN_DW),      // SYNC_NUM > PRBS_N/IN_DW
parameter           LOS_NUM                 = 8,    // when err exceeds LOS_NUM, deassert ptrn_sync
parameter           BREAK_NUM               = 32,   // when no in_vld asserted during BREAK_NUM clk, deassert ptrn_sync

// the following parameters are calculated automatically
parameter           PRBS_DW                 = MAX_F(PRBS_N, IN_DW)
) ( 
input                                       rst_n,
input                                       clk,
input                                       cke,

output  reg         [PRBS_DW-1:0]           prbs_dat,

input                                       in_vld,
input               [IN_DW-1:0]             in_dat,

input                                       cfg_clr,
output  reg                                 ptrn_sync,
output  reg                                 prbs_err
);

//################################################################################
// define local varialbe and localparam
//################################################################################
localparam          SYNC_BW                 = $clog2(SYNC_NUM);
localparam          LOS_BW                  = $clog2(LOS_NUM);
localparam [SYNC_BW-1:0]    SYNC_NUM_DEC    = SYNC_NUM-1;
localparam [LOS_BW-1:0]     LOS_NUM_DEC     = LOS_NUM-1;

reg                 [PRBS_DW-1:0]           prbs_dat_sel;
wire                [IN_DW-1:0]             exp_dat;
wire                                        same;
reg                 [SYNC_BW-1:0]           cnt_same;
wire                                        same_enough;
reg                 [LOS_BW-1:0]            cnt_miss;
wire                                        miss_enough;
wire                                        ptrn_dead;
wire                                        brk_enough;

//################################################################################
// main
//################################################################################

always@(*) begin
    prbs_dat_sel = prbs_dat;

    if (ptrn_sync==1'b0) begin
        if (MSB_FIRST==1'b1)
            prbs_dat_sel[        0+:IN_DW] = in_dat;
        else
            prbs_dat_sel[PRBS_DW-1-:IN_DW] = in_dat;
    end
end

always@(posedge clk or negedge rst_n) begin
    if (rst_n==1'b0) begin
        prbs_dat <=`U_DLY {PRBS_DW{PRBS_INI}};
    end else if (cke==1'b1) begin
        if (cfg_clr==1'b1)
            prbs_dat <=`U_DLY {PRBS_DW{PRBS_INI}};
        else if (in_vld==1'b1)
            prbs_dat <=`U_DLY prbs_nxt_f(prbs_dat_sel, PRBS_N, SHIFT_N, PRBS_INI, MSB_FIRST);
        else
            ;
    end else
        ;
end

assign exp_dat = (MSB_FIRST==1'b1) ? prbs_dat[        0+:IN_DW] :
                                     prbs_dat[PRBS_DW-1-:IN_DW];
                                     
assign same = (exp_dat==in_dat) ? 1'b1 : 1'b0;

always@(posedge clk or negedge rst_n) begin
    if (rst_n==1'b0) begin
        cnt_same <=`U_DLY {SYNC_BW{1'b0}};
    end else if (cke==1'b1) begin
        if (cfg_clr==1'b1)
            cnt_same <=`U_DLY {SYNC_BW{1'b0}};
        else if ((in_vld==1'b1) && (same==1'b0))
            cnt_same <=`U_DLY {SYNC_BW{1'b0}};
        else if ((in_vld==1'b1) && (same_enough==1'b0))
            cnt_same <=`U_DLY cnt_same + 1'd1;
        else
            ;
    end else
        ;
end

assign same_enough = (cnt_same>=SYNC_NUM_DEC) ? same : 1'b0;

always@(posedge clk or negedge rst_n) begin
    if (rst_n==1'b0) begin
        cnt_miss <=`U_DLY {LOS_BW{1'b0}};
    end else if (cke==1'b1) begin
        if (cfg_clr==1'b1)
            cnt_miss <=`U_DLY {LOS_BW{1'b0}};
        else if ((in_vld==1'b1) && (same==1'b0) && (ptrn_sync==1'b1))
            cnt_miss <=`U_DLY cnt_miss + 1'd1;
        else if ((in_vld==1'b1) && (same_enough==1'b1))
            cnt_miss <=`U_DLY {LOS_BW{1'b0}};
        else
            ;
    end else
        ;
end
assign miss_enough = (cnt_miss>=LOS_NUM_DEC) ? (~same) : 1'b0;

generate if (BREAK_NUM>=2) begin:G_BRK

    localparam          BRK_BW                  = $clog2(BREAK_NUM);
    localparam [BRK_BW-1:0]     BRK_NUM_DEC     = BREAK_NUM-1;
    
    reg                 [BRK_BW-1:0]            cnt_brk;

    always@(posedge clk or negedge rst_n) begin
        if (rst_n==1'b0) begin
            cnt_brk <=`U_DLY {BRK_BW{1'b0}};
        end else if (cke==1'b1) begin
            if (in_vld==1'b1)
                cnt_brk <=`U_DLY {BRK_BW{1'b0}};
            else if (ptrn_sync==1'b1) 
                cnt_brk <=`U_DLY cnt_brk + 1'b1;
            else
                ;
        end else
            ;
    end
    assign brk_enough = (cnt_brk>=BRK_NUM_DEC) ? (~in_vld) : 1'b0;

end else begin:G_NBRK

    assign brk_enough = 1'b0;

end endgenerate

assign ptrn_dead = (PRBS_INI==1'b0) ? (&prbs_dat[0+:PRBS_DW]) :
                                    (~(|prbs_dat[0+:PRBS_DW])) ;
always@(posedge clk or negedge rst_n) begin
    if (rst_n==1'b0) begin
        ptrn_sync <=`U_DLY 1'b0;
    end else if (cke==1'b1) begin
        if (cfg_clr==1'b1 || brk_enough==1'b1 || ptrn_dead==1'b1)
            ptrn_sync <=`U_DLY 1'b0;
        else if ((ptrn_sync==1'b0) && (in_vld==1'b1) && (same_enough==1'b1))
            ptrn_sync <=`U_DLY 1'b1;
        else if ((ptrn_sync==1'b1) && (in_vld==1'b1) && (miss_enough==1'b1))
            ptrn_sync <=`U_DLY 1'b0;
        else
            ;
    end else
        ;
end

always@(posedge clk or negedge rst_n) begin
    if (rst_n==1'b0) begin
        prbs_err <=`U_DLY 1'b0;
    end else if (cke==1'b1) begin
        prbs_err <=`U_DLY ptrn_sync & in_vld & (~same);
    end
end

`include "func_prbs.v"  // refer to prbs_nxt_f()
`include "func_param.v" // refer to MAX_F()

//################################################################################
// ASSERTION
//################################################################################

`ifdef CBB_ASSERT_ON
// synopsys translate_off

a_sync_num: assert property (@(posedge clk) disable iff (!rst_n)
    ((SYNC_NUM*IN_DW)>PRBS_N)
) else begin
    $error("SYNC_NUM is too small.");
    $stop;
end

// synopsys translate_on
`endif

endmodule
